package org.snackframework.mapping;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletContext;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.snackframework.core.ScreenCacheElement;
import org.snackframework.util.PathUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

public class DefaultModuleMapping<K,V> implements ModuleMapping<K,V>
{
	private String[] packages;
	
	private static Map moduleMap=Collections.synchronizedMap(new HashMap<String,ScreenCacheElement>());
	
	private static Log log=LogFactory.getLog(DefaultModuleMapping.class);
	
	private ClassLoader classLoader;
	
	public DefaultModuleMapping(String[] packages,ClassLoader classLoader)
	{
		this.packages=packages;
		this.classLoader=classLoader;
	}
	
	public DefaultModuleMapping(ClassLoader classLoader)
	{
		this.classLoader=classLoader;
	}
	
	@Override
	public Object get(K key)
	{
		return moduleMap.get(key);
	}

	@Override
	public void put(K key, V value)
	{
		moduleMap.put(key, value);
	}

	@Override
	public void setPackages(String[] packages)
	{
		this.packages = packages;
	}
	
	@Override
	public void initMapping(ServletContext servletContext)
	{
		if(ArrayUtils.isEmpty(this.packages))
		{
			log.info("DefaultModuleMapping packages null , skip initMapping.");
			return ;
		}
		
		//scan packages
		/*
		 * TODO
		 * 如果多个packages下面有同名的类，则会产生问题
		 * 
		 */
		for(String pack:this.packages)
		{
			String realPath=PathUtils.getClassPath(servletContext, pack);
			File file=new File(realPath);
			if(!file.isDirectory())
			{
				throw new RuntimeException("请检查配置文件中 moduleScanDirectory 属性的值是否是一个文件目录,包名!而不应该是一个具体的类名!");
			}
			
			scanPackage(classLoader,file,"/");
		}
	}
	
	protected void scanPackage(ClassLoader cl,File file,String url)
	{
		File[] fileList=file.listFiles(new FileFilter(){
			@Override
			public boolean accept(File pathname)
			{
				if(pathname.isDirectory() || -1!=pathname.getName().indexOf(".class"))
				{
					return true;
				}
				return false;
			}
		});
		
		for(File f:fileList)
		{
			if(f.isFile())
			{
				// load class
				
				load(f,url+(url.endsWith("/")?"":("/"))+PathUtils.lowerCaseFirstCharacter(f.getName().split("\\.")[0]));
			}
			
			else if(f.isDirectory())
			{
				scanPackage(cl,f,url+(url.endsWith("/")?"":("/"))+f.getName());
			}
		}
	}
	
	protected void load(File file,String url)
	{
		try
		{
			Class clazz=classLoader.loadClass(PathUtils.getFilePath(file));
			moduleMap.put(url, new ScreenCacheElement(clazz,url));
		}
		catch (ClassNotFoundException e)
		{
			log.error("",e);
			e.printStackTrace();
		}
	}
	
	public void instanceAllSingletonModule(ServletContext servletContext)
	{
		long start=System.currentTimeMillis();
		WebApplicationContext webApplicationContext=WebApplicationContextUtils.getWebApplicationContext(servletContext);
		Iterator  iterator=DefaultModuleMapping.moduleMap.keySet().iterator();
		try
		{
			while(iterator.hasNext())
			{
				ScreenCacheElement sce=(ScreenCacheElement)DefaultModuleMapping.moduleMap.get(iterator.next());
				if(sce.isSingleton())
				{
					if(!sce.isInstantiated())
					{
						// create instance
						sce.createSingletonInstance();
						
						initProperty(sce.getObject(),sce.getClazz(),webApplicationContext);
						
						sce.finishInitProperty();
					}
				}
			}
			long end=System.currentTimeMillis();
			System.out.println("\ninstance total "+moduleMap.size()+" modules,cost: "+(end-start)+"ms\n");
		}
		catch(IllegalAccessException e)
		{
			e.printStackTrace();
		}
		
	}
	
	/*
	 * 考虑了有继承关系的类的层次结构
	 */
	public void initProperty(Object instance,Class clazz,WebApplicationContext webApplicationContext) throws IllegalAccessException
	{
		// init property
		Field[] fields=clazz.getDeclaredFields();
		for(Field field:fields)
		{
			field.setAccessible(true);
			field.set(instance, webApplicationContext.getBean(field.getName()));
		}
		if(clazz.getSuperclass()!=null)
		{
			if(!clazz.getSuperclass().getName().startsWith("java.") && !clazz.getSuperclass().getName().startsWith("javax."))
			{
				initProperty(instance,clazz.getSuperclass(),webApplicationContext);
			}
		}
	}
}
